package eval

import (
	"showen/lex"
	"showen/obj"
	"showen/parse"
	"testing"
)

func TestLetRec(t *testing.T) {
	tests := []struct {
		input    string
		expected string
	}{
		{
			"(letrec ((fact (lambda (n) (if (= n 0) 1 (* n (fact (- n 1))))))) (fact 5))",
			"120",
		},
		{
			"(letrec ((even? (lambda (n) (if (= n 0) #t (odd? (- n 1))))) (odd? (lambda (n) (if (= n 0) #f (even? (- n 1)))))) (even? 4))",
			"#t",
		},
		{
			"(letrec ((even? (lambda (n) (if (= n 0) #t (odd? (- n 1))))) (odd? (lambda (n) (if (= n 0) #f (even? (- n 1)))))) (odd? 5))",
			"#t",
		},
		{
			"(letrec ((x 10)) x)",
			"10",
		},
		{
			"(letrec ((f (lambda () x)) (x 10)) (f))",
			"10",
		},
	}

	env := obj.NewEnvironment()
	RegisterBuiltinProcedures(env)

	for _, tt := range tests {
		lexer := lex.NewLexer(tt.input)
		tokens := lexer.ReadTokens()
		parser := parse.NewParser(tokens)
		expr, err := parser.ParseExpression()
		if err != nil {
			t.Fatalf("parser.ParseExpression() error = %v", err)
		}

		obj, err := EvalExpression(expr, env)
		if err != nil {
			t.Fatalf("Eval() error = %v", err)
		}
		if obj.Inspect() != tt.expected {
			t.Errorf("Eval() = %v, want %v", obj.Inspect(), tt.expected)
		}
	}
}